/**
 * @ProjectName: 
 * @Copyright: 2014 lisheng  All Right Reserved.
 * @address: toughheart@163.com
 * @date: 2015年1月2日 下午8:21:18
 * @Description: 本内容未经本人允许禁止使用、转发.
 */

package org.apache.lucene.demo;

import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import org.apache.commons.lang3.StringUtils;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.facet.Facets;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.IndexWriterConfig.OpenMode;
import org.apache.lucene.index.IndexableField;
import org.apache.lucene.index.LogDocMergePolicy;
import org.apache.lucene.index.LogMergePolicy;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.SearcherFactory;
import org.apache.lucene.search.SearcherLifetimeManager;
import org.apache.lucene.search.SearcherManager;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.search.TopScoreDocCollector;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.store.NIOFSDirectory;
import org.apache.lucene.util.NamedThreadFactory;
import org.apache.lucene.util.Version;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 
 * @author lisheng
 * @date 2015年1月2日 下午8:21:18
 * @version V1.0
 */
public class LuceneServerImpl implements LuceneServer {

	public static final Version VERSION = Version.LATEST;

	protected Logger logger = LoggerFactory.getLogger(this.getClass());

	protected IndexWriter indexWriter = null;// 索引写入器

	protected IndexReader indexReader = null;// 索引读取器

	protected SearcherManager searcherManager;
	
	protected SearcherLifetimeManager lifetimeMGR = new SearcherLifetimeManager();
	
	protected Directory dir = null;

	protected String address = "";// 索引文件路径

	protected boolean opened = false;// 是否打开索引文件

	protected boolean autoCommit = false;// 是否自动提交

	protected boolean nearRealTime = true;
	
	protected long time = System.currentTimeMillis();
	
	private byte[] lockWirter = new byte[1];
	private byte[] lockOpen = new byte[1];
	
	private ExecutorService service = null;

	{
		service = new ThreadPoolExecutor(5, 20, 0L, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<Runnable>(),
                new NamedThreadFactory("IndexSearcher"));
	}
	
	
	protected final SearcherFactory factory = new SearcherFactory() {
	      @Override
	      public IndexSearcher newSearcher(IndexReader r) throws IOException {
	        IndexSearcher s = new IndexSearcher(r, service);
	        return s;
	      }
	};
	
	public LuceneServerImpl() {
		super();
	}

	public LuceneServerImpl(String address) {
		super();
		this.address = address;
	}

	public LuceneServerImpl(Directory dir) {
		super();
		this.dir = dir;
	}

	public LuceneServerImpl(IndexWriter indexWriter, IndexReader indexReader) {
		super();
		this.indexWriter = indexWriter;
		this.dir = this.indexWriter.getDirectory();
		this.indexReader = indexReader;
	}

	public LuceneServer reOpenIndexReader() {
		this.closeIndexReader();
		try {
			this.indexReader = DirectoryReader.open(dir);
		} catch (IOException e) {
			throw new RuntimeException(e.getMessage(), e);
		}
		return this;
	}

	

	@Override
	public IndexWriter getIndexWriter() {
		return this.indexWriter;
	}

	public void setIndexWriter(IndexWriter indexWriter) {
		this.indexWriter = indexWriter;
	}

	@Override
	public IndexReader getIndexReader() {
		return this.indexReader;
	}

	public void setIndexReader(IndexReader indexReader) {
		this.indexReader = indexReader;
	}

	@Override
	public void close() throws IOException {
		this.closeIndexReader();
		this.closeIndexWriter();
		if(searcherManager != null){
			searcherManager.close();
		}
		if(lifetimeMGR != null){
			lifetimeMGR.close();
		}
		if (dir != null) {
			this.dir.close();
		}
		if(service != null){
			try {
				service.shutdown();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}

	@Override
	public void closeIndexWriter() {
		if (this.indexWriter != null) {
			logger.info("关闭indexWriter....");
			try {
				this.indexWriter.close();
			} catch (IOException e) {
				throw new RuntimeException(e.getMessage(), e);
			}
		}
		indexWriter = null;
	}

	@Override
	public void closeIndexReader() {
		if (this.indexReader != null) {
			logger.info("关闭IndexReader....");
			try {
				this.indexReader.close();
			} catch (IOException e) {
				throw new RuntimeException(e.getMessage(), e);
			}
		}
		indexReader = null;
	}

	@Override
	public void rollback() {
		if (this.indexWriter != null) {
			logger.info("关闭indexWriter....");
			try {
				this.indexWriter.rollback();
			} catch (IOException e) {
				throw new RuntimeException(e.getMessage(), e);
			}
		}
	}

	@Override
	public void commit() {
		if (this.indexWriter != null ) {
			try {
				long time = System.currentTimeMillis();
				this.indexWriter.commit();
				this.logger.debug(this.getAddress()+"commit耗时："+(System.currentTimeMillis()-time));
			} catch (IOException e) {
				throw new RuntimeException(e.getMessage(), e);
			}
		}
	}

	/**
	 * @return the autoCommit
	 */
	@Override
	public boolean isAutoCommit() {
		return this.autoCommit;
	}

	/**
	 * @param autoCommit
	 *            the autoCommit to set
	 */
	public void setAutoCommit(boolean autoCommit) {
		this.autoCommit = autoCommit;
	}

	@Override
	public Directory getDir() {
		return dir;
	}

	@Override
	public Directory openDir(String path) {
		try {
			File file = new File(path);
			if(!file.exists()){
				file.mkdirs();
			}
			String name = System.getProperty("os.name");
			if(name != null && name.indexOf("Windows")>-1){
				this.dir = FSDirectory.open(new File(path));
			}else{
				this.logger.info("使用NIO!");
				this.dir =  NIOFSDirectory.open(new File(path));
			}
//			   NRTCachingDirectory cachedFSDir = new NRTCachingDirectory(fsDir, 5.0, 60.0);
//			this.dir = new RAMDirecstory(dir,IOContext.DEFAULT);
		} catch (IOException e) {
			throw new RuntimeException(e.getMessage(), e);
		}
		return dir;
	}

	@Override
	public String getAddress() {
		return this.address;
	}

	public LuceneServer setAddress(String address) {
		this.address = address;
		return this;
	}

	@Override
	public boolean isOpened() {
		return this.opened;
	}

	public void setOpened(boolean opened) {
		this.opened = opened;
	}

	public LuceneServer setDir(Directory dir) {
		this.dir = dir;
		return this;
	}

	@Override
	public boolean open() {
		return this.open(address);
	}

	@Override
	public boolean open(String address) {
		return this.open(address, AnalyzerUtils.getInstance(),
				OpenMode.CREATE_OR_APPEND);
	}
	
	 

	@Override
	public boolean open(String address, Analyzer analyzer, OpenMode openMode) {
		synchronized (lockOpen) {
			this.logger.info("打开服务"+address+"....");
			long start = System.currentTimeMillis();
			if (dir == null && StringUtils.isNotBlank(address)) {
				dir = this.openDir(address);
			}
			if (this.indexWriter == null && dir != null) {
				//如果目录下没有文件,则新建索引文件  
				File file = new File(this.address);
				if(file.isDirectory() && file.listFiles().length == 0){  
					// Create a new index in the directory, removing any
					// previously indexed documents:
					this.openIndexWriter(analyzer, OpenMode.CREATE);
					this.commit();
					this.closeIndexWriter();
				}  
				//如果已有索引文件则用原有的索引文件  OpenMode.CREATE_OR_APPEND
				// Add new documents to an existing index:
				this.openIndexWriter(analyzer, openMode);
			}
			if (this.indexReader == null && dir != null) {
				
				try {
					if(nearRealTime && this.indexWriter != null){
						this.indexReader = DirectoryReader.open(this.indexWriter,true);
					}else{
						this.indexReader = DirectoryReader.open(dir);
					}
				} catch (IOException e) {
					throw new RuntimeException(e.getMessage(), e);
				}
			}
			try {
				if(searcherManager == null){
					if(nearRealTime && this.indexWriter != null){
						this.searcherManager = new SearcherManager(this.indexWriter, true, factory);
					}else{
						if(this.indexWriter != null){
							// SearcherManager needs to see empty commit:
							indexWriter.commit();
						}
						this.searcherManager = new SearcherManager(this.dir, factory);
					}
				}
			} catch (IOException e) {
				throw new RuntimeException(e.getMessage(), e);
			}
			opened = true;
			logger.info("打开服务"+address+"耗时:"+(System.currentTimeMillis()-start));
		}
		return opened;
	}
	
	 
	@Override
	public LuceneServer openIndexWriter(Analyzer analyzer, OpenMode openMode) {
		LogMergePolicy mergePolicy = new LogDocMergePolicy();  
        // 索引基本配置  
        // 设置segment添加文档(Document)时的合并频率  
        // 值较小,建立索引的速度就较慢  
        // 值较大,建立索引的速度就较快,>10适合批量建立索引  
        mergePolicy.setMergeFactor(30);  
        // 设置segment最大合并文档(Document)数  
        // 值较小有利于追加索引的速度  
        // 值较大,适合批量建立索引和更快的搜索  
        mergePolicy.setMaxMergeDocs(5000);  
		IndexWriterConfig iwc = new IndexWriterConfig(VERSION, analyzer);
		iwc.setOpenMode(openMode);
		//IndexWriterConfig indexWriterConfig=new IndexWriterConfig(Version.LUCENE_43,new StandardAnalyzer(Version.LUCENE_43));  
        iwc.setMaxBufferedDocs(1000);  
        iwc.setMergePolicy(mergePolicy);  
        iwc.setRAMBufferSizeMB(64);
        try {
			if(IndexWriter.isLocked(dir)){
				IndexWriter.unlock(dir);
			}
		} catch (IOException e1) {
			e1.printStackTrace();
		}
        if(indexWriter == null){
           synchronized (lockWirter) {
        	   if(indexWriter == null){
		        	try {
		    			indexWriter = new IndexWriter(this.dir, iwc);
		    		} catch (IOException e) {
		    			throw new RuntimeException(e.getMessage(), e);
		    		}
		        	if(openMode != OpenMode.CREATE){
		        		Thread t  = new Thread(new Runnable(){

							@Override
							public void run() {
								try {
									if(opened && indexWriter.hasDeletions()){
										indexWriter.forceMergeDeletes();
									}
									indexWriter.maybeMerge();
									indexWriter.deleteUnusedFiles();
								} catch (IOException e) {
									e.printStackTrace();
								}
							}
		        		});
		        		t.setDaemon(true);
		        		t.start();
		        	}
        	   }
        	}
		}
		
		return this;
	}
	
	
	@Override
	public boolean reopen() {
		try {
			this.close();
		} catch (IOException e) {
			throw new RuntimeException(e.getMessage(), e);
		}
		return this.open();
	}

	@Override
	public boolean reopen(String address) {
		try {
			this.close();
		} catch (IOException e) {
			throw new RuntimeException(e.getMessage(), e);
		}
		return this.open(address);
	}

	@Override
	public boolean reopen(String address, Analyzer analyzer, OpenMode openMode) {
		try {
			this.close();
		} catch (IOException e) {
			throw new RuntimeException(e.getMessage(), e);
		}
		return this.open(address, analyzer, openMode);
	}

	@Override
	public Analyzer getDefaultAnalyzer() {
		return AnalyzerUtils.getInstance();
	}

	@Override
	public void addDoc(Document doc) {
		IndexWriter indexWriter = null;
		try {
			indexWriter = this.getIndexWriter();
			this.addDoc(indexWriter, doc);
			if (this.isAutoCommit()) {
				indexWriter.commit();
			}
		} catch (IOException e) {
			if (this.isAutoCommit()) {
				try {
					indexWriter.rollback();
				} catch (IOException e1) {
					throw new RuntimeException(e1.getMessage(), e1.getCause());
				}
			}
			throw new RuntimeException(e.getMessage(), e);
		} finally {

		}
	}

	@Override
	public void addDoc(IndexWriter indexWriter, Document doc) {
		try {
			indexWriter.addDocument(doc);
		} catch (IOException e) {
			throw new RuntimeException(e.getMessage(), e);
		} finally {

		}
	}

	@Override
	public void addDoc(IndexWriter indexWriter, List<IndexableField> list) {
		if (list != null) {
			Document doc = new Document();
			for (IndexableField indexableField : list) {
				doc.add(indexableField);
			}
			this.addDoc(indexWriter, doc);
		}

	}

	@Override
	public void addDoc(List<IndexableField> list) {
		IndexWriter indexWriter = null;
		try {
			indexWriter = this.getIndexWriter();
			this.addDoc(indexWriter, list);
			if (this.isAutoCommit()) {
				indexWriter.commit();
			}
		} catch (IOException e) {
			if (this.isAutoCommit()) {
				try {
					indexWriter.rollback();
				} catch (IOException e1) {
					throw new RuntimeException(e1.getMessage(), e1.getCause());
				}
			}
			throw new RuntimeException(e.getMessage(), e);
		} finally {

		}
	}

	@Override
	public void addDocs(IndexWriter indexWriter, List<Document> docs) {
		if (docs != null) {
			try {
				for (Document document : docs) {
					this.addDoc(indexWriter, document);
				}
			} finally {
			}
		}
	}

	@Override
	public void addDocs(List<Document> docs) {
		IndexWriter indexWriter = null;
		try {
			indexWriter = this.getIndexWriter();
			this.addDocs(indexWriter, docs);
			if (this.isAutoCommit()) {
				indexWriter.commit();
			}
		} catch (IOException e) {
			if (this.isAutoCommit()) {
				try {
					indexWriter.rollback();
				} catch (IOException e1) {
					throw new RuntimeException(e1.getMessage(), e1.getCause());
				}
			}
			throw new RuntimeException(e.getMessage(), e);
		} finally {

		}
	}

	@Override
	public void deleteAllDoc() {
		IndexWriter indexWriter = null;
		try {
			indexWriter = this.getIndexWriter();
			this.deleteAllDoc(indexWriter);
			if (this.isAutoCommit()) {
				indexWriter.commit();
			}
		} catch (IOException e) {
			if (this.isAutoCommit()) {
				try {
					indexWriter.rollback();
				} catch (IOException e1) {
					throw new RuntimeException(e1.getMessage(), e1.getCause());
				}
			}
			throw new RuntimeException(e.getMessage(), e);
		} finally {

		}
	}

	@Override
	public void deleteAllDoc(IndexWriter indexWriter) {
		try {
			indexWriter.deleteAll();
		} catch (IOException e) {
			throw new RuntimeException(e.getMessage(), e);
		} finally {
		}
	}

	@Override
	public void deleteDoc(IndexWriter indexWriter, Query... query) {
		try {
			indexWriter.deleteDocuments(query);
		} catch (IOException e) {
			throw new RuntimeException(e.getMessage(), e);
		} finally {
		}
	}

	@Override
	public void deleteDoc(IndexWriter indexWriter, Term... term) {
		try {
			indexWriter.deleteDocuments(term);
		} catch (IOException e) {
			throw new RuntimeException(e.getMessage(), e);
		} finally {
		}
	}

	@Override
	public void deleteDoc(Query... query) {
		IndexWriter indexWriter = null;
		try {
			indexWriter = this.getIndexWriter();
			this.deleteDoc(indexWriter, query);
			if (this.isAutoCommit()) {
				indexWriter.commit();
			}
		} catch (IOException e) {
			if (this.isAutoCommit()) {
				try {
					indexWriter.rollback();
				} catch (IOException e1) {
					throw new RuntimeException(e1.getMessage(), e1.getCause());
				}
			}
			throw new RuntimeException(e.getMessage(), e);
		} finally {

		}
	}

	@Override
	public void deleteDoc(Term... term) {
		IndexWriter indexWriter = null;
		try {
			indexWriter = this.getIndexWriter();
			this.deleteDoc(indexWriter, term);
			if (this.isAutoCommit()) {
				indexWriter.commit();
			}
		} catch (IOException e) {
			if (this.isAutoCommit()) {
				try {
					indexWriter.rollback();
				} catch (IOException e1) {
					throw new RuntimeException(e1.getMessage(), e1.getCause());
				}
			}
			throw new RuntimeException(e.getMessage(), e);
		} finally {

		}
	}

	@Override
	public TopDocs search(IndexSearcher searcher, Query query, int page,
			int pageSize) {
		TopScoreDocCollector topCollector = TopScoreDocCollector.create(
				searcher.getIndexReader().maxDoc(), false);// 返回所有条数的一个集合
		try {
			searcher.search(query, topCollector);
		} catch (IOException e) {
			throw new RuntimeException(e.getMessage(), e);
		}
		// System.out.println("一共有多少条记录命中:"+topCollector.getTotalHits());
		int start = (page - 1) * pageSize;// start:开始条数 pageSize：显示多少条
		return topCollector.topDocs(start, pageSize);
	}

	@Override
	public TopDocs search(IndexSearcher searcher, Query query, int pageSize) {
		try {
			return searcher.search(query, pageSize);
		} catch (IOException e) {
			throw new RuntimeException(e.getMessage(), e);
		}
	}

	
	@Override
	public void updateDoc(IndexWriter indexWriter, Term term, Document doc) {
		try {
			// Add new documents to an existing index:
			// indexWriter =
			// this.getIndexWriter(analyzer,OpenMode.CREATE_OR_APPEND);
			indexWriter.updateDocument(term, doc, getDefaultAnalyzer());
		} catch (IOException e) {
			throw new RuntimeException(e.getMessage(), e);
		} finally {
		}
	}

	@Override
	public void updateDoc(Term term, Document doc) {
		IndexWriter indexWriter = null;
		try {
			indexWriter = this.getIndexWriter();
			this.updateDoc(indexWriter, term, doc);
			if (this.isAutoCommit()) {
				indexWriter.commit();
			}
		} catch (IOException e) {
			if (this.isAutoCommit()) {
				try {
					indexWriter.rollback();
				} catch (IOException e1) {
					throw new RuntimeException(e1.getMessage(), e1.getCause());
				}
			}
			throw new RuntimeException(e.getMessage(), e);
		} finally {

		}
	}

	public SearcherManager getSearcherManager() {
		return searcherManager;
	}

	public void setSearcherManager(SearcherManager searcherManager) {
		this.searcherManager = searcherManager;
	}

	@Override
	public boolean isNearRealTime() {
		return nearRealTime;
	}

	public LuceneServerImpl setNearRealTime(boolean nearRealTime) {
		this.nearRealTime = nearRealTime;
		return this;
	}
	

	@Override
	public void release(IndexSearcher indexSearcher) {
		if (indexSearcher != null) {
			try {
				searcherManager.release(indexSearcher);
			} catch (IOException e) {
				throw new RuntimeException(e.getMessage(), e);
			}
		} 
	}

	@Override
	public IndexSearcher acquire() {
		try {
			IndexSearcher s = searcherManager.acquire();
			return s;
		} catch (IOException e) {
			throw new RuntimeException(e.getMessage(), e);
		}
	}

	public long getTime() {
		return time;
	}

	public void setTime(long time) {
		this.time = time;
	}
	@Override
	public String toString() {
		return "LuceneServerImpl [address=" + address + ", opened=" + opened
				+ ", nearRealTime=" + nearRealTime + ", time=" + time + "]";
	}

	public void refushTimestamp(){
		time = System.currentTimeMillis();
		try {
//			this.searcherManager.maybeRefreshBlocking();
			this.searcherManager.maybeRefresh();
			this.logger.debug(this.getAddress()+"刷新耗时："+(System.currentTimeMillis()-time));
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	@Override
	public Facets searchFacet(Query query) throws IOException {
		// TODO Auto-generated method stub
		return null;
	}
	 
}
